package cn.lg.soar.common.util.token;

import cn.lg.soar.common.algorithm.CryptoUtils;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.jackson.JacksonUtil;

import java.time.Duration;
import java.util.Base64;

/**
 * 自定义的JWT
 * 快速解析校验( 20um )
 * 载体支持任意对象
 * 校验结果信息丰富，不需二次解析
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class SoarJWT {

    public static final String JOIN = ".";
    public static final String SPLIT = "\\.";

    private final static Base64.Encoder BASE64URLEN = DataUtil.BASE64URLEN;
    private final static Base64.Decoder BASE64URLDE = DataUtil.BASE64URLDE;


    /**
     * 有效时长(ms)
     */
    public final long expire;

    /**
     * 解码器
     */
    public final Decoder decoder;

    public SoarJWT(String secret, long expire) {
        this.decoder = new Decoder(CryptoUtils.HMAC256E(secret.getBytes()));
        this.expire = expire;
    }

    public SoarJWT(String secret, Duration expire) {
        this(secret, expire.getSeconds() * 1000);
    }

    public SoarJWT(CryptoUtils.Encoder encoder, long expire) {
        this.decoder = new Decoder(encoder);
        this.expire = expire;
    }

    /**
     * 创建token
     * @param payload 载体
     * @param salt 客户端盐
     * @return
     */
    public String create(String payload, String salt){
        String expiresTime = String.valueOf(this.expire + System.currentTimeMillis());
        String payloadEn = BASE64URLEN.encodeToString(payload.getBytes()).replaceAll("=", "");
        return this.decoder.sign(expiresTime + salt + payload) + JOIN + expiresTime + JOIN + payloadEn;
    }

    /**
     * 创建token
     * @param payload 载体
     * @param salt 客户端盐
     * @return
     */
    public String create(Object payload, String salt){
        return create(JacksonUtil.toJson(payload), salt);
    }

    /**
     * 创建token
     * @param payload 载体
     * @return
     */
    public String create(String payload){
        return create(payload, "");
    }

    /**
     * 创建token
     * @param payload 载体
     * @return
     */
    public String create(Object payload){
        return create(JacksonUtil.toJson(payload), "");
    }


    /**
     * 校验token
     * @param token
     * @return
     */
    public Result verify(String token){
        return this.decoder.verify(token, "");
    }

    /**
     * 校验token
     * @param token
     * @param salt 客户端盐
     * @return
     */
    public Result verify(String token, String salt){
        return this.decoder.verify(token, salt);
    }

    /**
     * 校验token（忽略有效期）
     * @param token
     * @return
     */
    public Result verifyIgnoreTime(String token){
        return this.decoder.verifyIgnoreTime(token, "");
    }

    /**
     * 校验token（忽略有效期）
     * @param token
     * @param salt 客户端盐
     * @return
     */
    public Result verifyIgnoreTime(String token, String salt){
        return this.decoder.verifyIgnoreTime(token, salt);
    }

    /**
     * 解析获取签名
     * @param token
     * @return
     */
    public static String parseSign(String token){
        return token.split(SPLIT)[0];
    }

    /**
     * 解析获取有效期
     * @param token
     * @return
     */
    public static long parseExpiresTime(String token){
        return Long.parseLong(token.split(SPLIT)[1]);
    }

    /**
     * 解析获取载体
     * @param token
     * @return
     */
    public static String parsePayload(String token){
        return new String(BASE64URLDE.decode(token.split(SPLIT)[2]));
    }

    /**
     * 解析获取载体
     * @param token
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T parsePayload(String token, Class<T> clazz) {
        return JacksonUtil.fromJson(parsePayload(token), clazz);
    }

    /**
     * 解码器
     * 独立出来方便实现动态 secret
     */
    public static class Decoder{

        private CryptoUtils.Encoder encoder;

        public Decoder(CryptoUtils.Encoder encoder) {
            this.encoder = encoder;
        }

        /**
         * 签名
         * @param origin
         * @return
         */
        public String sign(String origin) {
            return BASE64URLEN.encodeToString(
                    encoder.sign(origin.getBytes())
            ).replaceAll("=", "");
        }

        /**
         * 校验
         * @param token
         * @param salt 客户端盐
         * @return
         */
        public Result verify(String token, String salt){
            String[] strings = token.split(SPLIT);
            if(strings.length < 3){
                return new Result("format");
            }
            long expiresTime = Long.parseLong(strings[1]);
            //验证有效期
            if(expiresTime < System.currentTimeMillis()){
                return new Result("expires");
            }
            //解析载体
            strings[2] = new String(BASE64URLDE.decode(strings[2]));
            //验证签名
            if(sign(strings[1] + salt + strings[2]).equals(strings[0])){
                return new Result(strings[2], expiresTime, strings[0]);
            }
            return new Result("InvalidSignature");
        }

        /**
         * 校验
         * @param token
         * @return
         */
        public Result verify(String token){
            return verify(token, "");
        }

        /**
         * 校验忽略有效期
         * @param token
         * @return
         */
        public Result verifyIgnoreTime(String token, String salt){
            String[] strings = token.split(SPLIT);
            if(strings.length < 3){
                return new Result("format");
            }
            //解析载体
            strings[2] = new String(BASE64URLDE.decode(strings[2]));
            //验证签名
            if(sign(strings[1] + salt + strings[2]).equals(strings[0])){
                return new Result(strings[2], Long.parseLong(strings[1]), strings[0]);
            }
            return new Result("InvalidSignature");
        }
        public Result verifyIgnoreTime(String token){
            return verifyIgnoreTime(token, "");
        }
    }

    /**
     * token的校验结果
     */
    public static final class Result {

        /**
         * 载体
         */
        private String payload;

        /**
         * 到期时间
         */
        private long expiresTime;

        /**
         * 是否有效
         */
        private boolean valid;

        /**
         * 结果信息
         */
        private String message;

        public Result(String message) {
            this.valid = false;
            this.message = message;
        }

        public Result(String payload, long expiresTime, String message) {
            this.valid = true;
            this.payload = payload;
            this.expiresTime = expiresTime;
            this.message = message;
        }

        /**
         * 获取载体
         * @param clazz
         * @param <T>
         * @return
         */
        public <T> T getPayload(Class<T> clazz) {
            return JacksonUtil.fromJson(payload, clazz);
        }

        /**
         * 获取字符串载体
         * @return
         */
        public String getPayload() {
            return payload;
        }

        /**
         * 获取有效期
         * @return
         */
        public long getExpiresTime() {
            return expiresTime;
        }

        /**
         * 是否有效
         * @return
         */
        public boolean isValid() {
            return valid;
        }

        /**
         * 获取信息
         * @return
         */
        public String getMessage() {
            return message;
        }

        /**
         * 是否无效的
         * @return
         */
        public boolean invalid(){
            return !valid;
        }

        @Override
        public String toString() {
            return "Results{" +
                    "payload='" + payload + '\'' +
                    ", expiresTime=" + expiresTime +
                    ", valid=" + valid +
                    ", message='" + message + '\'' +
                    '}';
        }
    }
}
